#include "cuda_header.h"
#include "GPU2BWT_api.h"


#define GPU_RESULT_BUF_SIZE ((unsigned long)1500000000)

GPU2BWT::GPU2BWT(BWT* h_bwt, BWT* h_revbwt, 
		const unsigned long GPUBufSize) :
		_GPUBufSize(GPUBufSize), 
		_GPUResultBufSize(GPU_RESULT_BUF_SIZE) {
	assert(_GPUBufSize > _GPUResultBufSize);
	
	//initialize the GPU index
	GPU2BWTInitIndex(&_d_bwt, &_d_revbwt, h_bwt, h_revbwt, &_GPUMemBuf, _GPUBufSize);

	//initialize the GPU memory buffer
	_GPUMemBuf = new st_MemoryBuf(_GPUBufSize, GMEM);

	numResultPerQueryPass1 = 2;
	numResultPerQueryPass2 = 128;
}


GPU2BWT::GPU2BWT(BWT* h_bwt, BWT* h_revbwt) :
		_GPUBufSize(0), 
		_GPUResultBufSize(GPU_RESULT_BUF_SIZE) {
	
	//initialize the GPU index
	GPU2BWTInitIndex(&_d_bwt, &_d_revbwt, h_bwt, h_revbwt, &_GPUMemBuf, _GPUBufSize);

	//just allocation new memory bvuffer
	_GPUMemBuf = NULL;
}

GPU2BWT::~GPU2BWT() {

}

unsigned long GPU2BWT::numAlignment(SAResult &saResult) const {
	unsigned long numAlign = 0;
	for(unsigned int i = 0; i < saResult.numSA; i++) {
		numAlign += ((saResult.getSA())[i].z - (saResult.getSA())[i].y + 1);
	}
	return numAlign;
}

void GPU2BWT::report(SAResult &saResult) const {
	cout << "The number of SA ranges:  " << saResult.numSA << endl;
	cout << "The number of alignments: " << numAlignment(saResult) << endl;
}

unsigned long GPU2BWT::exactMatch(SAResult &saResult, QuerySet &querySet) {
	//prepare the memory buffers
	_GPUMemBuf->reset();
	char* h_mask = new char[querySet.numQuery];
	memset(h_mask, 0, sizeof(char)*querySet.numQuery);
	QueryHelper helper(querySet.query, h_mask, 
				querySet.numQuery, querySet.readLength, 
				_GPUMemBuf, _GPUResultBufSize);
	
	//the cuda kernel
	cuda_exactMatch(saResult, helper, _d_bwt, _d_revbwt);

	delete[] h_mask;

	return numAlignment(saResult);
}

unsigned long GPU2BWT::oneMismatch(SAResult &saResult, SAMatch &saMatch, 
				QuerySet &querySet) {
	//prepare the memory buffers
	_GPUMemBuf->reset();
	char* h_mask = new char[querySet.numQuery];
	memset(h_mask, 0, sizeof(char)*querySet.numQuery);
	QueryHelper helper(querySet.query, h_mask, 
				querySet.numQuery, querySet.readLength,
				_GPUMemBuf, _GPUResultBufSize);

	//the cuda kernel
	//cuda_1mismatch(saResult, helper, _d_bwt, _d_revbwt);
	new_cuda_1mismatch(saResult, saMatch, helper, _d_bwt, _d_revbwt);

	delete[] h_mask;

	return numAlignment(saResult);
}


	
unsigned long GPU2BWT::twoMismatches(SAResult &saResult, QuerySet &querySet) { 
	//prepare the memory buffers
	_GPUMemBuf->reset();
	char* h_mask = new char[querySet.numQuery];
	memset(h_mask, 0, sizeof(char)*querySet.numQuery);
	QueryHelper helper(querySet.query, h_mask, 
				querySet.numQuery, querySet.readLength, 
				_GPUMemBuf, _GPUResultBufSize);

	//the cuda kernel
	cuda_2mismatch(saResult, helper, _d_bwt, _d_revbwt);

	delete[] h_mask;

	return numAlignment(saResult);
}


void GPU2BWT::setNumResultPerQuery(const int numPass1, const int numPass2) {
	numResultPerQueryPass1 = numPass1;
	numResultPerQueryPass2 = numPass2;
}

unsigned long GPU2BWT::allValid(SAResult &saResult, QuerySet &querySet, int Mode) {

	unsigned int timer = 0;
	int numAlignExact, numAlign1Mis, numAlign2Mis;

	startTimer(&timer);
	//kernel options
	kernelConfig();

	//prepare the memory buffers
	char* h_mask = new char[querySet.numQuery];
	memset(h_mask, 0, sizeof(char)*querySet.numQuery);
	QueryHelper helper;
	if(_GPUMemBuf != NULL) {
		_GPUMemBuf->reset();
		helper = QueryHelper(querySet.query, h_mask, querySet.numQuery, 
				querySet.readLength, 
				_GPUMemBuf, _GPUResultBufSize); 
	} else {
		cout << "Do not use GPUMemBuf." << endl;
		//helper = QueryHelper(querySet.query, h_mask, querySet.numQuery, 
		//		querySet.readLength, 
		//		_GPUResultBufSize);
		cout << "Not supported yet." << endl;
		exit(-1);
	}

	cutilSafeCall(cudaThreadSynchronize());
	endTimer(&timer, "\tkernel initialization");

	if(Mode == THREAD_BUF) {
	//matching, as the kernel append results, we can call them in sequence
		timer = 0;
		startTimer(&timer);
		cuda_exactMatch(saResult, helper, _d_bwt, _d_revbwt);
		numAlignExact = saResult.numAlignment();
		cutilSafeCall(cudaThreadSynchronize());
		endTimer(&timer, "\tcuda_exactMatch");

		timer = 0;
		startTimer(&timer);
		helper.reset(querySet.query, h_mask);
		cuda_1mismatch(saResult, helper, _d_bwt, _d_revbwt);
		numAlign1Mis = saResult.numAlignment() - numAlignExact;
		cutilSafeCall(cudaThreadSynchronize());
		endTimer(&timer, "\tcuda_1mismatch");

		timer = 0;
		startTimer(&timer);
		helper.reset(querySet.query, h_mask);
		cuda_2mismatch(saResult, helper, _d_bwt, _d_revbwt);
		numAlign2Mis = saResult.numAlignment() - numAlignExact - numAlign1Mis;
		cutilSafeCall(cudaThreadSynchronize());
		endTimer(&timer, "\tcuda_2mismatch");
	}
	else if(Mode == QUERY_BUF) {
		cout<<"2 pass, pass1 threshold: " << numResultPerQueryPass1 <<
			" pass2 threshold: "<<numResultPerQueryPass2 <<  endl;
	
		timer = 0;
		startTimer(&timer);
		cuda_exactMatch(saResult, helper, _d_bwt, _d_revbwt);
		numAlignExact = saResult.numAlignment();
		cutilSafeCall(cudaThreadSynchronize());
		endTimer(&timer, "\tcuda_exactMatch");
			
		timer = 0;
		startTimer(&timer);
		helper.reset(querySet.query, h_mask);
		cuda_1mismatch_2pass(saResult, helper, _d_bwt, _d_revbwt, numResultPerQueryPass1, numResultPerQueryPass2, &querySet);
		numAlign1Mis = saResult.numAlignment() - numAlignExact;
		cutilSafeCall(cudaThreadSynchronize());
		endTimer(&timer, "\tcuda_1mismatch_pass");

		timer = 0;
		startTimer(&timer);
		helper.reset(querySet.query, h_mask);
		cuda_2mismatch_2pass(saResult, helper, _d_bwt, _d_revbwt, 
					numResultPerQueryPass1, numResultPerQueryPass2, &querySet);
		numAlign2Mis = saResult.numAlignment() - numAlignExact - numAlign1Mis;
		cutilSafeCall(cudaThreadSynchronize());
		endTimer(&timer, "\tcuda_2mismatch_2pass");
	}

	cout << "The number of alignment:" << saResult.numAlignment() << endl;
	cout << "	- Exact match:  " << numAlignExact << endl;
	cout << "	- 1 mismatch:   " << numAlign1Mis << endl;
	cout << "	- 2 mismatches: " << numAlign2Mis << endl;

	delete[] h_mask;

	return numAlignment(saResult);
}


unsigned long GPU2BWT::randomBest(SAResult &saResult, QuerySet &querySet) {

	kernelConfig();

	//prepare the memory buffers
	const int numRead = querySet.numRead();
	_GPUMemBuf->reset();
	char* mask = new char[querySet.numQuery];
	memset(mask, 0, sizeof(char)*querySet.numQuery);
	QueryHelper helper(querySet.query, mask, 
			querySet.numQuery, querySet.readLength, 
			_GPUMemBuf, _GPUResultBufSize);

	/*matching, as the kernel append results, we can call them in sequence*/
	cuda_exactMatch(saResult, helper, _d_bwt, _d_revbwt);
	rb_updateMask(mask, saResult, querySet);
	const int numAlignExact = sum(mask, numRead);

	helper.reset(querySet.query, mask);
	cuda_1mismatch(saResult, helper, _d_bwt, _d_revbwt);
	rb_updateMask(mask, saResult, querySet);
	const int numAlign1Mis = sum(mask, numRead) - numAlignExact;

	helper.reset(querySet.query, mask);
	cuda_2mismatch(saResult, helper, _d_bwt, _d_revbwt);
	rb_updateMask(mask, saResult, querySet);
	const int numAlign2Mis = sum(mask, numRead) - numAlignExact - numAlign1Mis;

	/*clean the SA result, make each read only has one SA range*/
	rb_updateSA(saResult, querySet, mask);


	cout << "The number of alignment:" << sum(mask, numRead) << endl;
	cout << "	- Exact match:  " << numAlignExact << endl;
	cout << "	- 1 mismatch:   " << numAlign1Mis << endl;
	cout << "	- 2 mismatches: " << numAlign2Mis << endl;

	delete[] mask;	
}


void GPU2BWT::rb_updateMask(char* mask, SAResult &saResult, QuerySet &querySet) {
	unsigned long rid = 0;
	for(unsigned long i = 0; i < saResult.numSA; i++) {
		rid = querySet.getRid(saResult.getSA(i).x);
		mask[rid] = 1;
		mask[rid + querySet.numRead()] = 1;	
	}
}


void GPU2BWT::rb_updateSA(SAResult &saResult, QuerySet &querySet, 
		const char* mask) {
	vector<uint3> finalSA;
	char* done = new char[querySet.numRead()];	
	memset(done, 0, sizeof(char)*querySet.numRead());
	int rid = 0;
	uint3 sa;

	for(unsigned int i = 0; i < saResult.numSA; i++) {
		rid = querySet.getRid(saResult.getSA(i).x);
		if(!done[rid]) {
			sa = saResult.getSA(i);
			sa.z = sa.y;	//output only one alignment
			finalSA.push_back(sa);	
			done[rid] = 1;
		}
	}
	saResult.update(&(finalSA.front()), finalSA.size());

	delete[] done;
}

void GPU2BWT::getNumAlignmentPerRead(int* numAlignPerRead, 
		const SAResult &saResult, 
		const QuerySet &querySet) {
	const unsigned int numRead = querySet.numRead();
	for(unsigned long i = 0; i < numRead; i++) {
		numAlignPerRead[i] = 0;
	}

	for(unsigned long i = 0; i < saResult.numSA; i++) {
		const int rid = querySet.getRid(saResult.getSA(i).x);
		numAlignPerRead[rid] += saResult.getNumAlignment(i);
	}
}

